/*  ---------------------------------
 |  THIS IS BUT A CODE CENTIPEDE |
 ---------------------------------
    O----- O----- O----- O----- O-----
    |  |   |  |   |  |   |  |   |  |
   _|  |_ _|  |_ _|  |_ _|  |_ _|  |_
 */
#ifdef ENABLE_DIFFNORM

#if defined (_WINDOWS)
#include <direct.h>
#include <windows.h>
#elif (defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)))
#include <sys/stat.h>
#endif

// -- qtils
#include <RandomUtils.h>
#include <Config.h>
#include <FileUtils.h>
#include <StringUtils.h>
#include <SystemUtils.h>
#include <TimeUtils.h>
#include <logger/Log.h>

// -- bass
#include <Bass.h>
#include <db/ClassedDatabase.h>
#include <db/DbFile.h>
#include <isc/IscFile.h>
#include <isc/ItemSetCollection.h>
#include <itemstructs/CoverSet.h>

#include "../blocks/diffnorm/DiffNormAlgo.h"
#include "../blocks/krimp/codetable/CodeTable.h"

#include "../FicMain.h"

#include "DiffNormTH.h"

DiffNormTH::DiffNormTH(Config *conf) : TaskHandler(conf) {

}

DiffNormTH::~DiffNormTH() {
    // not my Config*
}

void DiffNormTH::HandleTask() {
    string command = mConfig->Read<string>("command");

    if (command.compare(DiffNormAlgo::NAME) == 0) Compress(mConfig);
    else THROW("Unable to handle task `" + command + "`");
}

string DiffNormTH::BuildWorkingDir() {
    return mConfig->Read<string>("taskclass") + "/";
}

// Originally, FicMain::Compress

void DiffNormTH::Compress(Config *config, const string tagIn) {
    // selName	`mushroom-cls-20d-nop`		SelectionName
    // iscName	`mushroom-cls-20d`			ItemSetCollectionName
    // dbName	`mushroom`					DatabaseName

    // Determine ISC properties
    string iscName = config->Read<string>("iscName");

    // Determine DB properties
    string dbName = config->Read<string>("dbname", iscName.substr(0, iscName.find_first_of('-')));
    ItemSetType dataType = ItemSet::StringToType(config->Read<string>("datatype", ""));
    DbFileType dbType = DbFile::StringToType(config->Read<string>("dbinencoding", ""));

    // Determine Pruning Strategy
    string pruneStrategyStr = config->Read<string>("prunestrategy", "nop");

    string tag, timetag;
    if (tagIn.length() == 0) {
        timetag = TimeUtils::GetTimeStampString();
        tag = iscName;
    } else {
        tag = tagIn;
        timetag = tag.substr(tag.find_last_of('-') + 1, tag.length() - 1 - tag.find_last_of('-'));
    }

    // Read DB
    Database *db = Database::RetrieveDatabase(dbName, dataType, dbType);

    // Get ISC
    string iscFullPath = Bass::GetWorkingDir() + iscName + ".isc";

    ItemSetCollection *isc = NULL;
    string iscIfMinedStr = config->Read<string>("iscifmined", "");
    bool loadAll = config->Read<bool>("loadall", 0); // ??? sander-add
    bool iscBeenMined = false;
    bool isAlgoDN = config->Read<string>("algo").compare(0, 8, DiffNormAlgo::NAME) == 0;
    if (isAlgoDN) { // We don't really need a itemset collection...
        stringstream ss;
        ss << dbName << "-all-" << db->GetNumTransactions() + 1 << "d";
        iscName = ss.str();
        iscIfMinedStr = "zap"; // Otherwise, we can crash... don't feel the urgent need to fix this properly ;-(
    }

    try {
        uint8 ol = Bass::GetOutputLevel();
        if (isAlgoDN) { // We don't really need a itemset collection...
            Bass::SetOutputLevel(0);
        }
        IscFileType storeIscFileType = config->KeyExists("iscStoreType") ? IscFile::ExtToType(config->Read<string>("iscStoreType")) : BinaryFicIscFileType;
        IscFileType chunkIscFileType = config->KeyExists("iscChunkType") ? IscFile::ExtToType(config->Read<string>("iscChunkType")) : BinaryFicIscFileType;
        isc = FicMain::ProvideItemSetCollection(iscName, db, iscBeenMined, iscIfMinedStr.compare("store") == 0, loadAll, storeIscFileType, chunkIscFileType);
        if (isAlgoDN) { // We don't really need a itemset collection...
            Bass::SetOutputLevel(ol);
        }
    } catch (string msg) {
        delete db;
        throw msg;
    }

    DoCompress(config, db, isc, tag, timetag, 0, 0);
    delete db;
    delete isc;

    if (iscBeenMined == true && iscIfMinedStr.compare("zap") == 0) { // if stored, it's already gone from workingdir
        FileUtils::RemoveFile(iscFullPath);
    }
}

// Originally, FicMain::DoCompress

void DiffNormTH::DoCompress(Config *config, Database *db, ItemSetCollection *isc, const string &tag, const string &timetag, const uint32 resumeSup, const uint64 resumeCand) {
    // Build KrimpBase
    string algoName = config->Read<string>("algo");
    ClassedDatabase *cdb = new ClassedDatabase(db);
    Database **dbSplits = cdb->SplitOnClassLabels();
    uint8 numSplits = cdb->GetNumClasses();
    DiffNormAlgo *algo = DiffNormAlgo::CreateAlgo(db->GetDataType(), config, numSplits);

    if (algo == NULL)
        THROW("Unknown algorithm.");
    ECHO(1, printf("** Compression :: \n"));
    ECHO(3, printf(" * Initiating...\n"));


    // Determine tag, output dir & backup full config
    {
        string outDir = Bass::GetWorkingDir() + tag + "-" + algoName.substr(0, algoName.find_first_of('-')) + "-" + algo->GetShortName() + "-" + config->Read<string>("prunestrategy", "nop") + "-" + timetag + "/";
        FileUtils::CreateDir(outDir); /* check succes ? */

        string confFile = outDir + "/" + config->Read<string>("command") + "-" + timetag + ".conf";
        config->WriteFile(confFile);
        algo->SetOutputDir(outDir);
        algo->SetTag(tag);

        ECHO(1, printf(" * Experiment tag\t%s\n", tag.c_str()));
    }
    // Further configure KrimpBase
    ECHO(1, printf(" * Algorithm:\t\t%s", algoName.c_str()));
    {
        string pruneStrategyStr = config->Read<string>("prunestrategy");
        PruneStrategy pruneStrategy = KrimpAlgo::StringToPruneStrategy(pruneStrategyStr);
        string pruneStrategyLongName = KrimpAlgo::PruneStrategyToName(pruneStrategy);
        ECHO(1, printf(" %s\n", pruneStrategyLongName.c_str()))

        uint32 reportSup = config->Read<uint32>("reportsup", 0);
        uint32 reportCnd = config->Read<uint32>("reportcnd", 0);
        bool reportAcc = config->Read<bool>("reportAcc", false);

        ReportType reportSupWhat = KrimpAlgo::StringToReportType(config->Read<string>("reportsupwhat", "all"));
        ReportType reportCndWhat = KrimpAlgo::StringToReportType(config->Read<string>("reportcndwhat", "all"));
        ReportType reportAccWhat = KrimpAlgo::StringToReportType(config->Read<string>("reportaccwhat", "all"));

        CTInitType initCT = config->Read<bool>("initct", true) ? InitCTAlpha : InitCTEmpty;

        // TODO(kailash): ATM we hope that mCT is null in KrimpAlgo parent class
        algo->UseThisStuff(db, isc, db->GetDataType(), initCT, true);
        algo->BindCTsToDBs(dbSplits, db->GetDataType(), db->GetMaxSetLength());
        algo->SetPruneStrategy(pruneStrategy);
        algo->SetReporting(reportSup, reportCnd, reportAcc);
        algo->SetReportStyle(reportSupWhat, reportCndWhat, reportAccWhat);

        bool reportIter = config->Read<bool>("reportIter", false);
        ReportType reportIterWhat = KrimpAlgo::StringToReportType(config->Read<string>("reportiterwhat", "all"));
        algo->SetReportIter(reportIter);
        algo->SetReportIterStyle(reportIterWhat);
    }
    if (isc->GetNumItemSets() > 0)
        ECHO(1, printf(" * Candidates:\t\t%" I64d "p\n", isc->GetNumItemSets()));

    // Write Run-Time Info file
    char rti[100];
    sprintf_s(rti, 100, "__%s %s %s %s", FicMain::ficVersion.c_str(), algo->GetShortName().c_str(), ItemSet::TypeToString(db->GetDataType()).c_str(), algo->GetShortPruneStrategyName().c_str());
    string rtiFile = algo->GetOutDir() + "/" + rti;
    FILE *rtifp = fopen(rtiFile.c_str(), "w");
    fclose(rtifp); /* check success ? */

    algo->Run();
    delete algo;

    for(uint32 i=0; i<numSplits; i++) 
            delete dbSplits[i];
    delete[] dbSplits;
    delete cdb;
    
    ECHO(3, printf("** Finished compression.\n"));
}

#endif // ENABLE_DIFFNORM
